/**
 * @file isal_multithread_perf.c
 * @brief It is used to verify high speed algorithm saturation issue
 * @details
 *	usage: taskset -c <cpu_index1,cpu_index2,...> isal_multithread_perf -m <algorithm name> -n
 *<thread num> eg: taskset -c 0-9,20-29 ./isal_multithread_perf -m md5_mb -n 10
 */

#include <pthread.h>
#include <sys/time.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>

#include "isal_multithread_perf.h"

alg_method algs[] = { { "md5", md5_ossl_func, ISAL_MD5_MAX_LANES },
                      { "md5_mb", md5_mb_func, ISAL_MD5_MAX_LANES },
                      { "sha1", sha1_ossl_func, SHA1_MAX_LANES },
                      { "sha1_mb", sha1_mb_func, SHA1_MAX_LANES },
                      { "sha256", sha256_ossl_func, ISAL_SHA256_MAX_LANES },
                      { "sha256_mb", sha256_mb_func, ISAL_SHA256_MAX_LANES },
                      { "sha512", sha512_ossl_func, ISAL_SHA512_MAX_LANES },
                      { "sha512_mb", sha512_mb_func, SHA512_MAX_LANES },
                      { "cbc_128_dec", cbc_128_dec_func, 1 },
                      { "cbc_192_dec", cbc_192_dec_func, 1 },
                      { "cbc_256_dec", cbc_256_dec_func, 1 },
                      { "xts_128_enc", xts_128_enc_func, 1 },
                      { "xts_256_enc", xts_256_enc_func, 1 },
                      { "gcm_128_enc", gcm_128_enc_func, 1 },
                      { "gcm_256_enc", gcm_256_enc_func, 1 },

                      { NULL, NULL } };

/* Global parameters*/
long long run_secs = 10;
uint32_t num_threads = 2;
uint32_t buflen = 32 * 1024;
uint32_t prememcpy = 0;
uint32_t postmemcpy = 0;
char *method = "md5_mb";

/* Global thread sync */
pthread_mutex_t count_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t count_cond = PTHREAD_COND_INITIALIZER;
volatile uint32_t count = 0;

int verbose = 0;

void
usage(char *appname)
{
        int i = 0;
        printf("Usage: %s -n num_threads\n", appname);
        printf("\t-v verbose output\n"
               "\t-t time to run(secs)\n"
               "\t-n number of algorithm threads\n"
               "\t-l len of each buffer(KB)\n"
               "\t-a memory copy before algorithm -- 1 do(default); 0 not do\n"
               "\t-b memory copy after algorithm -- 1 do(default); 0 not do\n"
               "\t-m method of algorithm:");
        for (i = 0; algs[i].name != NULL; i++)
                printf("  %s", algs[i].name);
        printf("\n");
}

void
notice(char *appname, alg_method *alg_choose_p)
{
        int i = 0;
        printf("%s starts to run\n", appname);
        printf("\tverbose output is %d\n"
               "\truntime is %lld(secs)\n"
               "\tnumber of algorithm threads is %d\n"
               "\tlen of each buffer(KB) is %d\n"
               "\tmemory copy before algorithm is %d\n"
               "\tmemory copy after algorithm is %d\n"
               "\tmethod of algorithm is %s\n",
               verbose, run_secs, num_threads, buflen / 1024, prememcpy, postmemcpy,
               alg_choose_p->name);
}

int
main(int argc, char **argv)
{
        int i = 0;
        int opt;
        char *optstring = "t:n:m:l:a:b:v";
        int32_t *id = NULL, ret = 0;
        alg_method alg_choose;
        pthread_t *clients = NULL;
        uint64_t count = 0, sum = 0;
        uint32_t rounds_buf;

        while ((opt = getopt(argc, argv, optstring)) != -1) {
                switch (opt) {
                case 't':
                        run_secs = atol(optarg);
                        if (run_secs <= 0) {
                                usage(argv[0]);
                                exit(-1);
                        }
                        break;
                case 'n':
                        num_threads = atoi(optarg);
                        if (num_threads <= 0) {
                                usage(argv[0]);
                                exit(-1);
                        }
                        break;
                case 'm':
                        method = optarg;
                        break;
                case 'l':
                        buflen = atoi(optarg) * 1024;
                        if (buflen <= 0) {
                                usage(argv[0]);
                                exit(-1);
                        }
                        break;
                case 'a':
                        prememcpy = atoi(optarg);
                        if (prememcpy != 0 && prememcpy != 1) {
                                usage(argv[0]);
                                exit(-1);
                        }
                        break;
                case 'b':
                        postmemcpy = atoi(optarg);
                        if (postmemcpy != 0 && postmemcpy != 1) {
                                usage(argv[0]);
                                exit(-1);
                        }
                        break;
                case 'v':
                        verbose = 1;
                        break;
                default:
                        usage(argv[0]);
                        exit(0);
                }
        }

        /* Check method str and set algorithm_func */
        for (i = 0; algs[i].name != NULL; i++) {
                if (!strcmp(method, algs[i].name)) {
                        alg_choose = algs[i];
                        break;
                }
        }
        if (algs[i].name == NULL) {
                usage(argv[0]);
                exit(-1);
        }

        notice(argv[0], &alg_choose);
        rounds_buf = alg_choose.rounds_nbuf;

        clients = (pthread_t *) calloc(num_threads + 1, sizeof(pthread_t));
        id = (int32_t *) calloc(num_threads + 1, sizeof(int32_t));

        printf("Start %i threads, use %s function\n", num_threads, alg_choose.name);

        for (i = 0; i < num_threads; i++) {
                id[i] = i;

                ret = pthread_create(&clients[i], NULL, alg_choose.thread_func, (void *) &id[i]);

                if (ret != 0) {
                        printf("Failed to create thread %i: %s", i, strerror(ret));
                        exit(-1);
                }
                printfv("Thread %i is created\n", i);
        }

        for (i = 0; i < num_threads; i++) {
                pthread_join(clients[i], (void *) &count);
                sum += count;
        }
        double loop_unit = ((double) buflen) * rounds_buf / run_secs / 1024 / 1024;
        printf("Sum of rounds is %ld\n"
               "Average throughput(MB/s) is %.2f\n"
               "Total throughput(MB/s) is %.2f\n",
               sum, (double) sum / i * loop_unit, (double) sum * loop_unit);

        exit(0);
}
